
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <errno.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

#include "fat.h"
#include "dump.h"

static FILE *img_file;
static int first_file_cluster = -1;
static int n_file_clusters = 0;

static void error(const char *msg, const char *subject, void *data)
{
  if (subject)
    printf("ERROR: %s: %s\n", msg, subject);
  else
    printf("ERROR: %s\n", msg);
}

#if 0
/* use mmap */
static int write_file(const char *filename, int first_cluster, int n_clusters, void *data)
{
  FAT_INFO *fi = (FAT_INFO *) data;
  void *in_mem;
  int in_fd;
  int write_len, done_len;

  if (first_file_cluster < 0) {
    fseeko(img_file, fat_first_cluster_offset(fi) + fat_cluster_size(fi) * (first_cluster-2), SEEK_SET);
    first_file_cluster = first_cluster;
  }
  n_file_clusters += n_clusters;
  write_len = n_clusters*fat_cluster_size(fi);

  // open file
  in_fd = open(filename, O_RDONLY);
  if (in_fd < 0) {
    error("Can't open file", filename, data);
    return 1;
  }
  // mmap
  in_mem = mmap(NULL, write_len, PROT_READ, MAP_PRIVATE|MAP_NOCACHE, in_fd, 0);
  if (in_mem == MAP_FAILED) {
    error("Can't mmap file", filename, data);
    close(in_fd);
    return 1;
  }

  done_len = fwrite(in_mem, 1, write_len, img_file);
  if (done_len < write_len)
    fseeko(img_file, fat_first_cluster_offset(fi) + fat_cluster_size(fi) * (first_cluster+n_clusters-2), SEEK_SET);

  munmap(in_mem, write_len);
  close(in_fd);
  return 0;
}
#else
/* no mmap */
static int write_file(const char *filename, int first_cluster, int n_clusters, void *data)
{
  FAT_INFO *fi = (FAT_INFO *) data;
  FILE *f_in;
  char buf[4096];
  int write_len, done_len;

  if (first_file_cluster < 0) {
    fseeko(img_file, fat_first_cluster_offset(fi) + fat_cluster_size(fi) * (first_cluster-2), SEEK_SET);
    first_file_cluster = first_cluster;
  }
  n_file_clusters += n_clusters;
  write_len = n_clusters*fat_cluster_size(fi);

  // copy input file data to image clusters
  done_len = 0;
  f_in = fopen(filename, "r");
  if (f_in) {
    while (done_len < write_len) {
      int n, r, w;
      
      n = write_len - done_len;
      if (n > sizeof(buf))
	n = sizeof(buf);

      r = fread(buf, 1, n, f_in);
      if (r <= 0)
	break;
      w = fwrite(buf, 1, r, img_file);
      if (w <= 0) {
	error("Can't write to image file", filename, data);
	fclose(f_in);
	return 1;
      }
      done_len += w;
    }
    fclose(f_in);
  } else {
    error("Can't open file", filename, data);
  }

  fseeko(img_file, fat_first_cluster_offset(fi) + fat_cluster_size(fi) * (first_cluster+n_clusters-2), SEEK_SET);
  return 0;
}
#endif

int main(int argc, char *argv[])
{
  FAT_INFO fi;
  unsigned char zero[512];
  unsigned char *dir_clusters;
  int n_dir_clusters;
  unsigned char *fat;
  unsigned int fat_size;
  char *image_filename, *src_dir;
  unsigned long long disk_size;
  int ret, i;

  if (argc != 4) {
    printf("USAGE: %s file.img img-size src-dir\n", argv[0]);
    return 0;
  }
  image_filename = argv[1];
  disk_size = atoll(argv[2]);
  src_dir = argv[3];

  dump_set_output("-");

  // open output image
  img_file = fopen(image_filename, "w");
  if (! img_file) {
    printf("ERROR: can't open '%s'\n", image_filename);
    return 1;
  }

  // initialize fat
  ret = fat_init(&fi, disk_size, error, write_file, &fi);
  switch (ret) {
  case -2: printf("Bad disk size: %lld\n", disk_size); return 1;
  case -1: printf("Out of memory for FAT\n"); return 1;
  case 1:
    printf("*******************************************************\n");
    printf("WARNING: disk size not recommended (%u clusters)\n", fat_num_clusters(&fi));
    printf("*******************************************************\n");
    break;
  }
  printf("disk size = %1.7g MiB (%1.7g GiB)\n", fat_disk_size(&fi) / 1024. / 1024., fat_disk_size(&fi) / 1024. / 1024. / 1024.);
  printf("num_clusters = %u\n", fat_num_clusters(&fi));
  printf("FAT size = %u (%1.4g MiB)\n", fat_fat_size(&fi), fat_fat_size(&fi) / 1024. / 1024.);
  
  // build from source dir
  dir_clusters = fat_build_filesystem(&fi, src_dir, &n_dir_clusters);
  if (! dir_clusters) {
    fclose(img_file);
    fat_free(&fi);
    return 1;
  }

  //dump_data(0, dir_clusters, 512);
  //dump_data(0, dir_clusters + 64*512, 512);
  //dump_data(0, fi.fat, 512);

  // write reserved sectors
  fseeko(img_file, 0, SEEK_SET);
  memset(zero, 0, 512);
  fwrite(fat_get_bpb(&fi), 1, 512, img_file);
  fwrite(fat_get_fsi(&fi), 1, 512, img_file);
  for (i = 2; i < 6; i++)
    fwrite(zero, 1, 512, img_file);
  fwrite(fat_get_bpb(&fi), 1, 512, img_file);
  for (i = 7; i < 32; i++)
    fwrite(zero, 1, 512, img_file);

  // write FATs
  fat = fat_get_fat(&fi, &fat_size);
  fwrite(fat, 1, fat_size, img_file);
  fwrite(fat, 1, fat_size, img_file);

  // write directory clusters
  fwrite(dir_clusters, 1, n_dir_clusters*64*512, img_file);

  // fill rest with zeroes
  if (fat_num_clusters(&fi) != fat_num_used_clusters(&fi)) {
    fseeko(img_file, fat_disk_size(&fi) - 1, SEEK_SET);
    fputc(0, img_file);
  }

  free(dir_clusters);
  fat_free(&fi);
  fclose(img_file);
  return 0;
}
